﻿namespace PageTypeBuilder.SortOrderChecker
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Text;
    using Microsoft.Build.Utilities;

    public class SortOrderChecker
    {
        public enum RunType
        {
            CommandLine,
            BuildTask
        }

        public bool CheckSortOrders(RunType runType, string assemblyFolderPath, bool treatAsErrors, Task task)
        {
            DirectoryInfo assemblyFolder = new DirectoryInfo(assemblyFolderPath);

            if (!assemblyFolder.Exists)
                return true;

            LoadAssemblies(assemblyFolder);

            IEnumerable<Definition> pageTypeDefinitions = GetDefinitions();
            StringBuilder messageBuilder = new StringBuilder();
            int definedTotal = 0;
            int undefinedTotal = 0;

            foreach (Definition definition in pageTypeDefinitions)
            {
                // standard properties
                List<Tuple<bool, PropertyInfo, PropertyInfo>> propertyInformation = GetPageTypeProperties(definition.Type)
                    .Select(property => new Tuple<bool, PropertyInfo, PropertyInfo>(false, property, null))
                    .ToList();

                // property group properties
                foreach (PropertyInfo property in GetPropertyGroupProperties(definition.Type))
                {
                    propertyInformation.AddRange(GetPageTypeProperties(property.PropertyType)
                        .Select(propertyGroupProperty => new Tuple<bool, PropertyInfo, PropertyInfo>(true, propertyGroupProperty, property)));
                }

                Dictionary<string, Data> sortOrders = new Dictionary<string, Data>();
                Dictionary<string, Data> undefinedSortOrders = new Dictionary<string, Data>();

                foreach (Tuple<bool, PropertyInfo, PropertyInfo> propertyInfo in propertyInformation)
                {
                    bool isPropertyGroup = propertyInfo.Item1;
                    object attribute = null;
                    object propertyGroupAttribute = null;
                    string propertyGroupName = string.Empty;
                    string name = string.Empty;

                    if (isPropertyGroup)
                    {
                        attribute = GetPageTypePropertyAttribute(propertyInfo.Item2);
                        propertyGroupAttribute = GetPageTypePropertyGroupAttribute(propertyInfo.Item3);
                        propertyGroupName = propertyInfo.Item3.Name;
                        name = string.Format("{0}.{1}", propertyInfo.Item3.Name, propertyInfo.Item2.Name);
                    }
                    else
                    {
                        attribute = GetPageTypePropertyAttribute(propertyInfo.Item2);
                        name = propertyInfo.Item2.Name;
                    }

                    Data data = new Data
                    {
                        Attribute = attribute,
                        PropertyGroupAttribute = propertyGroupAttribute,
                        PropertyGroupName = propertyGroupName
                    };

                    if (data.GetSortOrder() > 0)
                        sortOrders.Add(name, data);
                    else
                        undefinedSortOrders.Add(name, data);
                }

                if (sortOrders.Count > 0 || undefinedSortOrders.Count > 0)
                {
                    bool outputSummary = false;
                    IEnumerable<IGrouping<int, int>> query = sortOrders
                        .Select(c => c.Value.GetSortOrder())
                        .GroupBy(c => c, c => c);

                    StringBuilder currentMessageBuilder = new StringBuilder();

                    foreach (IGrouping<int, int> grouping in query)
                    {
                        if (grouping.Count() > 1)
                        {
                            CreateSummary(currentMessageBuilder, ref outputSummary, definition);
                            currentMessageBuilder.AppendLine();
                            currentMessageBuilder.AppendLine(string.Format("Properties with sort order '{0}':", grouping.Key));
                            currentMessageBuilder.AppendLine();

                            foreach (var item in sortOrders.Where(c => c.Value.GetSortOrder() == grouping.Key))
                            {
                                string episerverPropertyName = item.Key.Replace(".", "-");
                                currentMessageBuilder.AppendLine(string.Format("{0} ({1})", item.Key, episerverPropertyName));
                                definedTotal++;
                            }
                        }
                    }

                    if (undefinedSortOrders.Count > 0)
                    {
                        CreateSummary(currentMessageBuilder, ref outputSummary, definition);
                        currentMessageBuilder.AppendLine();
                        currentMessageBuilder.AppendLine("Properties with an undefined sort order:");
                        currentMessageBuilder.AppendLine();

                        foreach (KeyValuePair<string, Data> item in undefinedSortOrders)
                        {
                            string episerverPropertyName = item.Key.Replace(".", "-");
                            currentMessageBuilder.AppendLine(string.Format("{0} ({1})", item.Key, episerverPropertyName));
                            undefinedTotal++;
                        }
                    }

                    string currentMessage = currentMessageBuilder.ToString().Trim();

                    if (currentMessageBuilder.Length > 0 && runType == RunType.BuildTask)
                    {
                        if (treatAsErrors)
                            task.Log.LogError(currentMessage);
                        else
                            task.Log.LogWarning(currentMessage);
                    }

                    messageBuilder.Append(currentMessageBuilder.ToString());
                }
            }

            StringBuilder summaryBuilder = new StringBuilder();

            summaryBuilder.AppendLine();
            summaryBuilder.AppendLine("*********************************************************");
            summaryBuilder.AppendLine(string.Format("{0} properties have duplicate sort orders", definedTotal));
            summaryBuilder.AppendLine(string.Format("{0} properties have undefined sort orders", undefinedTotal));
            summaryBuilder.AppendLine(string.Format("{0} properties have duplicate or undefined sort orders", definedTotal + undefinedTotal));
            summaryBuilder.AppendLine();

            if (definedTotal + undefinedTotal > 0 && task != null)
            {
                string summaryMessage = summaryBuilder.ToString();

                if (treatAsErrors)
                    task.Log.LogError(summaryMessage);
                else
                    task.Log.LogWarning(summaryMessage);
            }


            messageBuilder.Append(summaryBuilder.ToString());
            Console.Write(messageBuilder.ToString());

            return definedTotal + undefinedTotal == 0;
        }

        public static IEnumerable<PropertyInfo> GetPageTypeProperties(Type type)
        {
            return type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
                .Where(HasPageTypePropertyAttribute)
                .ToList();
        }

        public static IEnumerable<PropertyInfo> GetPropertyGroupProperties(Type type)
        {
            return type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
                .Where(HasPageTypePropertyGroupAttribute)
                .ToList();
        }

        private static void CreateSummary(StringBuilder messageBuilder, ref bool outputSummary, Definition definition)
        {
            if (outputSummary)
                return;

            messageBuilder.AppendLine();
            messageBuilder.AppendLine("*********************************************************");
            messageBuilder.AppendLine(string.Format("Page type: {0}", definition.PageTypeName));
            messageBuilder.AppendLine(string.Format("Full type name: {0}", definition.TypeName));
            outputSummary = true;
        }

        public static void LoadAssemblies(DirectoryInfo assemblyFolder)
        {
            List<FileInfo> files = assemblyFolder
                .GetFiles("*.dll")
                .Where(current => current.Name.EndsWith(".dll"))
                .ToList();

            //string location = Assembly.GetExecutingAssembly().Location;

            //if (!location.EndsWith(@"\"))
            //    location = location.Substring(0, location.LastIndexOf(@"\") + 1);

            //files.AddRange(new DirectoryInfo(@"C:\Program Files (x86)\EPiServer\Framework\6.2.267.1\bin\").GetFiles("*.dll"));
            //files.AddRange(new DirectoryInfo(@"C:\Program Files (x86)\EPiServer\CMS\6.1.379.0\bin\").GetFiles("*.dll"));

            foreach (FileInfo file in files)
            {
                try
                {
                    Console.WriteLine("Loading assembly '{0}'", file.FullName);
                    Assembly assembly = Assembly.LoadFrom(file.FullName);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Exception occurred while loading assembly '{0}'", file.FullName);
                }
            }
        }

        public static IEnumerable<Definition> GetDefinitions()
        {
            return AppDomain.CurrentDomain
                .GetAssemblies()
                .TypesWithPageTypeAttribute()
                .Concrete()
                .Select(type => new Definition
                {
                    Type = type,
                    Attribute = GetPageTypeAttribute(type)
                })
                .ToList();
        }

        public static object GetPageTypeAttribute(Type type)
        {
            try
            {
                return type.GetCustomAttributes(false).First(c => c.GetType().Name == "PageTypeAttribute");
            }
            catch (Exception ex)
            {
                return null;
            }
        }

        public static bool HasPageTypePropertyAttribute(PropertyInfo property)
        {
            try
            {
                return property.GetCustomAttributes(false).Any(c => c.GetType().Name == "PageTypePropertyAttribute");
            }
            catch (Exception ex)
            {
                return false;
            }
        }

        public static bool HasPageTypePropertyGroupAttribute(PropertyInfo property)
        {
            try
            {
                return property.GetCustomAttributes(false).Any(c => c.GetType().Name == "PageTypePropertyGroupAttribute");
            }
            catch (Exception ex)
            {
                return false;
            }
        }

        public static object GetPageTypePropertyAttribute(PropertyInfo property)
        {
            try
            {
                return property.GetCustomAttributes(false).First(c => c.GetType().Name == "PageTypePropertyAttribute");
            }
            catch (Exception ex)
            {
                return false;
            }
        }

        public static object GetPageTypePropertyGroupAttribute(PropertyInfo property)
        {
            try
            {
                return property.GetCustomAttributes(false).First(c => c.GetType().Name == "PageTypePropertyGroupAttribute");
            }
            catch (Exception ex)
            {
                return false;
            }
        }
    }
}
